#include <stdio.h>      // fopen
#include <stdlib.h>     // malloc  free
#include <string.h>     // strcpy
#include <stdarg.h>     // va_start
#include <sys/stat.h>   // mkdir
#include <time.h>       // time
#include <pthread.h>    // pthread

#include "logging.h"

#define MAX_LOG_FILE_SIZE (10*1024*1024)    // 日志文件大小最大10MB
#define MAX_LOG_FILE_NUM (8)                // 日志文件数量最多8个
#define LOG_FILENAME_LEN (64)               // 文件名长度
#define LOG_DIR "./logs"                    // 保存日志的目录
#define MAX_MSG_SIZE (128)                  // 消息长度
#define MAX_MSG_NUM (32*1024)               // 消息数量

typedef struct message_t {
    log_level_e level;                      // 等级
    time_t timestamp;                       // 时间戳
    char msg_buff[MAX_MSG_SIZE];            // 消息内容
} message_t;

typedef struct log_tcb_t {
    char log_filename[LOG_FILENAME_LEN];
    message_t msg_queue[MAX_MSG_NUM];
    unsigned int msg_w_idx;
    unsigned int msg_r_idx;
    unsigned int shutdown;
    pthread_mutex_t lock;
    pthread_cond_t notify;
    pthread_t thread;
} log_tcb_t;                    // log Thread Control Block

static log_tcb_t g_log_tcb = {{0}};

static inline long get_file_size(FILE *fp)
{
    fseek(fp, 0, SEEK_END);     // 将文件指针移动到文件末尾
    return ftell(fp);           // 获取当前位置（即文件大小）
}

static inline int timestamp_turn_filename(char *filename_buff,
    unsigned int len_filename_buff)
{
    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    if (NULL == t) {
        perror("Error in localtime()");
        return -1;
    }
    
    snprintf(filename_buff, len_filename_buff,
        "%s/%04d%02d%02d_%02d%02d%02d.log", LOG_DIR,
        t->tm_year +1900, t->tm_mon + 1, t->tm_mday, 
        t->tm_hour, t->tm_min, t->tm_sec);

    return 0;
}

static inline void log_write_to_file(FILE *fp, message_t* msg)
{
    char time_str[32];
    struct tm *tm_info = localtime(&msg->timestamp);
    strftime(time_str, sizeof(time_str),"%Y-%m-%d %H:%M:%S", tm_info);
    const char *level_str = "UNKNOWN";
    switch(msg->level) {
        case LOG_LEVEL_DEBUG:      level_str = "DEBUG";         break;
        case LOG_LEVEL_INFO:       level_str = "INFO";          break;
        case LOG_LEVEL_WARNING:    level_str = "WARNING";       break;
        case LOG_LEVEL_ERROR:      level_str = "ERROR";         break;
        default:        break;
    }
    
    fprintf(fp, "[%s][%s]%s\n", time_str, level_str, msg->msg_buff);
}

/* 日志线程函数 */
static void* log_thread_func(void* agrs)
{
    unsigned int read_index = 0, write_index = 0, msg_num_in_queue = 0;
    unsigned int exit_flag = 0, file_num = 0, i;
    char history_files[MAX_LOG_FILE_NUM][LOG_FILENAME_LEN] = {{0}};
    
    FILE *fp = fopen(g_log_tcb.log_filename, "w");
    if (NULL == fp) {
        perror("open log file Error!\n");
        return NULL;
    }
    memcpy(history_files[file_num++], g_log_tcb.log_filename, LOG_FILENAME_LEN);

    while(1)            // 循环处理消息, 消息处理完了会等待条件信号 notify
    {
        pthread_mutex_lock(&g_log_tcb.lock);
        if (g_log_tcb.shutdown) { exit_flag = 1; }
        read_index = g_log_tcb.msg_r_idx;
        write_index = g_log_tcb.msg_w_idx;
        while(!g_log_tcb.shutdown && write_index == read_index) {
            pthread_cond_wait(&g_log_tcb.notify, &g_log_tcb.lock);
            write_index = g_log_tcb.msg_w_idx;
        }
        g_log_tcb.msg_r_idx = write_index;
        pthread_mutex_unlock(&g_log_tcb.lock); 

        // 日志文件超过10MB则保存日志到新的文件
        if (get_file_size(fp) > MAX_LOG_FILE_SIZE) {
            fclose(fp);
            timestamp_turn_filename(g_log_tcb.log_filename, LOG_FILENAME_LEN);
            fp = fopen(g_log_tcb.log_filename, "w");
            if (file_num >= MAX_LOG_FILE_NUM) {
                // 删除往前数第 MAX_LOG_FILE_NUM 个日志文件
                remove(history_files[file_num % MAX_LOG_FILE_NUM]);
            }
            memcpy(history_files[file_num++ % MAX_LOG_FILE_NUM],
                    g_log_tcb.log_filename, LOG_FILENAME_LEN);
        }

        // 保存日志到文件
        msg_num_in_queue = write_index - read_index;
        for (i = 0; i < msg_num_in_queue; read_index++, i++) {
            log_write_to_file(fp, &g_log_tcb.msg_queue[read_index % MAX_MSG_NUM]);
        }
        
        if (exit_flag) {  // 如果使用 if (g_log_tcb.shutdown), 队列中可能会有残留消息
            fclose(fp);
            break;
        }
    }

    return NULL;
}

/* 创建日志线程 */
static int logging_thread_create(void)
{
    pthread_mutex_init(&g_log_tcb.lock, NULL);
    pthread_cond_init(&g_log_tcb.notify, NULL);
    return pthread_create(&g_log_tcb.thread, NULL, log_thread_func, NULL);
}

int logging_init(void)
{
#if __linux__
    mkdir(LOG_DIR, S_IRWXU);
#else
    mkdir(LOG_DIR);        // 建立存放日志的目录
#endif
    if (timestamp_turn_filename(g_log_tcb.log_filename, LOG_FILENAME_LEN)) {
        perror("generate log filename Error!\n");
        return -1;
    }

    g_log_tcb.shutdown = g_log_tcb.msg_w_idx = g_log_tcb.msg_r_idx = 0;

    if (logging_thread_create()) {
        perror("create logging thread Error!\n");
        return -1;
    }

    return 0;
}

/* main() 结束时清理日志线程 */
void logging_exit(void)
{
    pthread_mutex_lock(&g_log_tcb.lock);
    g_log_tcb.shutdown = 1;
    pthread_mutex_unlock(&g_log_tcb.lock);

    pthread_cond_broadcast(&g_log_tcb.notify);      // 唤醒日志线程
    pthread_join(g_log_tcb.thread, NULL);           // 等待日志线程退出
    
    pthread_mutex_destroy(&g_log_tcb.lock);
    pthread_cond_destroy(&g_log_tcb.notify);
}

int logging(log_level_e level, const char *format, ...)
{
    unsigned int write_index = 0;
    message_t * p_msg = NULL;

    if (level > LOG_LEVEL_DEBUG) {
        printf("logging level too big.\n");
        return -1;
    }

    pthread_mutex_lock(&g_log_tcb.lock);

    write_index = g_log_tcb.msg_w_idx;
    if (write_index - g_log_tcb.msg_r_idx == MAX_MSG_NUM) {
        pthread_mutex_unlock(&g_log_tcb.lock);
        printf("logging too much.\n");
        return -2;
    }
    g_log_tcb.msg_w_idx += 1;
    p_msg = &g_log_tcb.msg_queue[write_index % MAX_MSG_NUM];
    va_list args;
    va_start (args, format);
    vsnprintf(p_msg->msg_buff, MAX_MSG_SIZE, format, args);
    va_end(args);
    p_msg->level = level;
    p_msg->timestamp = time(NULL);

    pthread_cond_signal(&g_log_tcb.notify); // 必须写入队列后再通知线程
    pthread_mutex_unlock(&g_log_tcb.lock);

    return 0;
}
